from typing import TYPE_CHECKING, Sequence
import numpy as np
import functools

from puzzle.tile import TileDesc
from puzzle.mathF import Vec2
from container import nonBlankLabel_t

if TYPE_CHECKING:
    from puzzle.tile import Tile

class Board:
    def __init__(self, name: str, tiles: list[list["Tile"]] = []) -> None:
        self.name: str = name
        self.tiles: list[list[TileDesc]] = self.construct(tiles)
        self.tiles_flatten: list[TileDesc] = [td for row in self.tiles for td in row]
        
        self.__label_tile_map: dict[str, TileDesc] = {}
        self.__blank_tiles: list[TileDesc] = []
        for row in range(6):
            for td in self.tiles[row]:
                if td.tile.is_blank:
                    self.__blank_tiles.append(td)
                else:
                    self.__label_tile_map[td.tile.label] = td
    
    @functools.cache
    def __getitem__(self, index: Sequence[int]) -> TileDesc:
        row, col = index
        return self.tiles[row][col]
    
    def get_td_by_label(self, label: nonBlankLabel_t) -> TileDesc:
        return self.__label_tile_map[label]

    @functools.cache
    def get_adjacent_tiles(self, row: int, col: int) -> "list[TileDesc]":
        ret = []
        
        if not self.is_out_of_bound(row, col - 1):
            ret.append(self.__getitem__((row, col - 1)))
        if not self.is_out_of_bound(row, col + 1):
            ret.append(self.__getitem__((row, col + 1)))
        
        def _append(r: int, c: int):
            if not self.is_out_of_bound(r, c):
                ret.append(self.__getitem__((r, c)))
        
        if (row == 0) or (row == 1):
            if self.__getitem__((row, col)).is_facing_up:
                _append(row + 1, col + 1)
            else:
                _append(row - 1, col - 1)
        elif row == 2:
            if self.__getitem__((row, col)).is_facing_up:
                _append(row + 1, col)
            else:
                _append(row - 1, col - 1)
        elif row == 3:
            if self.__getitem__((row, col)).is_facing_up:
                _append(row + 1, col - 1)
            else:
                _append(row - 1, col)
        else:
            if self.__getitem__((row, col)).is_facing_up:
                _append(row + 1, col - 1)
            else:
                _append(row - 1, col + 1)
        
        return ret

    def get_adjacent_tiles_by_td(self, td: "TileDesc") -> "list[TileDesc]":
        return self.get_adjacent_tiles(td.row, td.col)
    
    @property
    @functools.cache
    def row_length(self) -> tuple[int, ...]:
        return tuple(map(len, self.tiles))
    
    @functools.cache
    def is_out_of_bound(self, row: int, col: int) -> bool:
        if not 0 <= row <= 5:
            return True
        if not 0 <= col <= self.row_length[row] - 1:
            return True
        return False
    
    @classmethod
    def construct(cls, tiles: list[list["Tile"]]) -> list[list[TileDesc]]:
        """ Construct Tile into TileDesc """

        _h = np.sin(np.pi / 3)
        def _get_vertices(row: int, col: int) -> tuple[Vec2, Vec2, Vec2]:
            flip = 1 if 0 <= row <= 2 else -1

            if 3 <= row <= 5:
                row = 5 - row

            if col % 2 == 0: # Down triangle
                x = - (3 + row) / 2
                
                vx = x + col // 2
                vy = _h * (3 - row)
                
                return (
                    Vec2(vx, vy * flip),
                    Vec2(vx - 0.5, (vy - _h) * flip),
                    Vec2(vx + 0.5, (vy - _h) * flip)
                )

            else: # Up triangle
                x = - (3 + row) / 2 - 0.5
                
                vx = x + col // 2 + 1
                vy = _h * (2 - row)
                
                return (
                    Vec2(vx, vy * flip),
                    Vec2(vx - 0.5, (vy + _h) * flip),
                    Vec2(vx + 0.5, (vy + _h) * flip)
                )
        
        ret: list[list[TileDesc]] = [[] for _ in range(6)]
        for row in range(6):
            for col in range(len(tiles[row])):
                va, vl, vr = _get_vertices(row, col)
                
                ret[row].append(TileDesc(
                    tiles[row][col],
                    row, col,
                    va, vl, vr
                ))
        return ret
